Skip to main content

SPI

This chapter covers device tree configuration, loopback testing, and communication with Python and C in Luckfox Lume SPI master mode.

1. SPI Subsystem

The Linux SPI subsystem is a core driver framework that manages and controls peripherals connected to SPI buses. Detailed documentation is available in the kernel source under <Linux_kernel_source>/Documentation/spi.

The SPI subsystem has two main components:

  1. sysfs interface The SPI subsystem exposes files and directories through sysfs to configure and manage SPI buses and devices. Relevant nodes are located under /sys/class/spi_master and /sys/bus/spi/devices. User-space applications can use this interface to read and modify SPI device attributes.
  2. User-space device nodes Each registered SPI device creates a corresponding character device node under /dev, allowing applications to exchange data with peripherals through standard file I/O. Device nodes are generally named /dev/spidevX.Y, where X is the SPI bus number and Y is the chip-select device number on that bus.

2. SPI Testing (Shell)

2.1 Pinout

SPICLKMISOMOSICS
SPI1Physical pin 23 / PD11Physical pin 21 / PD13Physical pin 19 / PD12CS0: Physical pin 24 / PD10

2.2 Device Tree Configuration

  1. Board-level device tree path:

    device/config/chips/t153/configs/luckfox_lume/linux-5.10-origin/board.dts
  2. Configure the SPI1 master and CS0 slave device as follows:

    &spi1 {
    pinctrl-0 = <&spi1_pins_default &spi1_pins_cs>;
    pinctrl-1 = <&spi1_pins_sleep>;
    pinctrl-names = "default", "sleep";
    sunxi,spi-bus-mode = <SUNXI_SPI_BUS_MASTER>;
    sunxi,spi-cs-mode = <SUNXI_SPI_CS_AUTO>;
    clock-frequency = <150000000>;
    sunxi,spi-num-cs = <1>;
    status = "okay";

    spidev@0 {
    compatible = "rohm,dh2228fv";
    reg = <0>;
    spi-max-frequency = <150000000>;
    spi-rx-bus-width = <1>;
    spi-tx-bus-width = <1>;
    status = "okay";
    };
    };
  3. Compile the device tree:

    sudo ./build.sh dts

2.3 Buildroot

  1. Add spidev in the buildroot directory. Search for the keyword "spidev".

    cd <Luckfox_Lume_SDK>/
    ./build.sh buildroot_menuconfig

  2. Select "spidev" in the search results, then save and exit.

  3. Build:

    sudo ./build.sh
    sudo ./build.sh pack

2.4 Viewing Devices

root@luckfox:~# ls /dev/spidev*
/dev/spidev1.0

2.5 SPI Loopback Test

Use the test tool included in the kernel. Connect pin 19 (MOSI) to pin 21 (MISO) with a jumper wire:

cd <Luckfox_Lume_SDK>/
export PATH="$PWD/out/toolchain/gcc-linaro-11.3.1-2022.06-x86_64_arm-linux-gnueabihf/bin:$PATH"
arm-linux-gnueabihf-gcc \
kernel/linux-5.10-origin/tools/spi/spidev_test.c \
-o spidev_test

Copy the program to the board, confirm that MOSI and MISO are connected, then run it:

chmod +x spidev_test
./spidev_test -D /dev/spidev1.0 -s 1000000 -b 8 -p "hello Lume!" -v

Output:

3. SPI Testing (Python)

  1. Example program:

    #!/usr/bin/env python3
    import sys
    import spidev


    def main():
    tx_buffer = list(b"hello Lume!")
    spi = spidev.SpiDev()
    try:
    spi.open(1, 0)
    spi.max_speed_hz = 1_000_000
    spi.mode = 0
    spi.bits_per_word = 8

    rx_buffer = spi.xfer2(tx_buffer)
    print("tx_buffer:", bytes(tx_buffer).decode("ascii"))
    print("rx_buffer:", bytes(rx_buffer).decode("ascii", errors="replace"))
    if rx_buffer != tx_buffer:
    print("Loopback FAIL")
    return 1
    print("Loopback PASS")
    return 0
    except OSError as error:
    print(f"SPI error: {error}", file=sys.stderr)
    return 1
    finally:
    spi.close()


    if __name__ == "__main__":
    sys.exit(main())
  2. Open the SPI device:

    spi.open(1, 0)
    spi.max_speed_hz = 1_000_000
    spi.mode = 0
    spi.bits_per_word = 8
    rx_buffer = spi.xfer2(tx_buffer)

    Configure 1 MHz, Mode 0, and 8-bit words, then perform one full-duplex transfer. xfer2() returns the received data.

  3. Run the program:

    python3 SPI.py

    Output:

4. SPI Loopback Test (C)

  1. Complete code:

    #include <fcntl.h>
    #include <linux/spi/spidev.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/ioctl.h>
    #include <unistd.h>

    int main(void)
    {
    const char *device = "/dev/spidev1.0";
    uint8_t tx_buffer[] = "hello Lume!";
    uint8_t rx_buffer[sizeof(tx_buffer)] = {0};
    const size_t length = sizeof(tx_buffer) - 1;
    uint8_t mode = SPI_MODE_0;
    uint8_t bits = 8;
    uint32_t speed = 1000000;
    int spi_file = open(device, O_RDWR);

    if (spi_file < 0) {
    perror("Failed to open SPI device");
    return EXIT_FAILURE;
    }

    if (ioctl(spi_file, SPI_IOC_WR_MODE, &mode) < 0 ||
    ioctl(spi_file, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0 ||
    ioctl(spi_file, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) {
    perror("Failed to configure SPI device");
    close(spi_file);
    return EXIT_FAILURE;
    }

    struct spi_ioc_transfer transfer = {
    .tx_buf = (uintptr_t)tx_buffer,
    .rx_buf = (uintptr_t)rx_buffer,
    .len = (uint32_t)length,
    .speed_hz = speed,
    .bits_per_word = bits,
    };

    int result = ioctl(spi_file, SPI_IOC_MESSAGE(1), &transfer);
    if (result < 0) {
    perror("Failed to perform SPI transfer");
    close(spi_file);
    return EXIT_FAILURE;
    }
    if (result != (int)length) {
    fprintf(stderr, "Incomplete SPI transfer: %d bytes\n", result);
    close(spi_file);
    return EXIT_FAILURE;
    }

    printf("tx_buffer: %s\n", (const char *)tx_buffer);
    printf("rx_buffer: %s\n", (const char *)rx_buffer);
    int matched = memcmp(tx_buffer, rx_buffer, length) == 0;
    puts(matched ? "Loopback PASS" : "Loopback FAIL");
    close(spi_file);
    return matched ? EXIT_SUCCESS : EXIT_FAILURE;
    }
  2. Open the SPI device:

    int spi_file = open(device, O_RDWR);

    Open /dev/spidev1.0 for reading and writing. If opening fails, report the error and exit.

  3. Configure SPI:

    ioctl(spi_file, SPI_IOC_WR_MODE, &mode);
    ioctl(spi_file, SPI_IOC_WR_BITS_PER_WORD, &bits);
    ioctl(spi_file, SPI_IOC_WR_MAX_SPEED_HZ, &speed);

    Set Mode 0, 8-bit words, and a 1 MHz clock. The complete program checks the return value of each configuration operation and does not transfer data if configuration fails.

  4. Send and receive data:

    int result = ioctl(spi_file, SPI_IOC_MESSAGE(1), &transfer);
    int matched = memcmp(tx_buffer, rx_buffer, length) == 0;
  5. Cross-compile:

    export PATH="<Luckfox_Lume_SDK>/out/toolchain/gcc-linaro-11.3.1-2022.06-x86_64_arm-linux-gnueabihf/bin:$PATH"
    arm-linux-gnueabihf-gcc -Wall -Wextra -O2 SPI.c -o SPI
  6. Run the program:

    chmod +x SPI
    ./SPI

    Output: